﻿#pragma once 

#include "Containers/Containers.h"

#include "Misc/StdHead.h"
#include "Math/Math.h"

#include "Material/Shader.h"
#include "Texture/Texture2D.h"
#include "Texture/TextureCube.h"

#include "Render/Material/RenderMaterial.h"
#include "Containers/ThreadSafePtr.h"

#include "Material.m.h"

RENUM()
enum class EMaterialParameterType
{
    Float,
    Vector2,
    Vector3,
    Vector4,

    Texture2D,
    TextureCube
};


RSTRUCT()
struct SMaterialParameterDescription
{
    ROBJECT

    SMaterialParameterDescription()=default;

    SMaterialParameterDescription(EMaterialParameterType InType,int64_t InOffset)
    {
        Type=InType;
        Offset=InOffset;
    }

    RFIELD()
    EMaterialParameterType Type=EMaterialParameterType::Float;

    //在数组中的索引
    //如果参数类型是纹理，这个值表示纹理在材质中的索引
    //如果参数类型是浮点数或者向量，这个值表示浮点数在材质中的索引
    RFIELD()
    int64_t Offset=0;
};


RCLASS()
class WHENGINE_API RMaterial: public RObject
{
    ROBJECT
public:

    struct SMaterialCreateInfo
    {
        TSharedObjectPtr<RShader> VertexShader;
        TSharedObjectPtr<RShader> FragmentShader;
        ERenderMaterialType PipelineType;
        TVector<float> FloatParameters;
        TVector<TSharedObjectPtr<RTexture2D>> Texture2Ds;
        TVector<TSharedObjectPtr<RTextureCube>> TextureCubes;
        TMap<CString,SMaterialParameterDescription> ParameterDescriptions;
        bool bDoubleSide=false;
    };


    static TSharedObjectPtr<RMaterial> Create(const SMaterialCreateInfo& InCreateInfo);


    RMaterial();

    // void SetVertexShader(TSharedObjectPtr<RShader> InVertexShader);
    // void SetFragmentShader(TSharedObjectPtr<RShader> InFragmentShader);
    // void SetType(ERenderMaterialType InType);
    // void SetFloatParameters(const TVector<float> & InParameters);
    // void SetTexture2Ds(const TVector<TSharedObjectPtr<RTexture2D>> & InTextures);

    void SyncParameterToRenderThread();
    void SyncToRenderThread();

    void PostLoadFromAsset() override;

    std::shared_ptr<CRenderMaterial> GetRenderMaterial();
    
    //copy the data from another material to this material
    void CopyFrom(TSharedObjectPtr<RMaterial> InMaterial); 


    //如何修改材质中的参数？
    //如果要修改材质中的参数，可以调用SetFloatParameter SetVector2Parameter SetVector3Parameter SetVector4Parameter SetTexture2DParameter
    //修改参数会影响所有使用这个材质的模型，
    //如果这个材质被多个模型使用，只想修改一个模型的参数，可以创建一个新的材质，然后修改新的材质的参数，然后将新的材质赋值给这个模型
    //示例代码：
    // auto NewMaterial=RMaterialSubsystem::GetInstance()->CopyMaterial(OldMaterial);
    // NewMaterial->SetFloatParameter("ParameterName",NewValue);
    // StaticMeshComponent->SetMaterial({NewMaterial});

    //设置着色器中使用的浮点参数的值
    RFUNCTION()
    void SetFloatParameter(CString ParameterName,float Value);

    //设置着色器中使用的二维向量参数的值
    RFUNCTION()
    void SetVector2Parameter(CString ParameterName, const SVec2 & Value);

    //设置着色器中使用的三维向量参数的值
    RFUNCTION()
    void SetVector3Parameter(CString ParameterName,const SVec3 & Value);

    //设置着色器中使用的四维向量参数的值
    RFUNCTION()
    void SetVector4Parameter(CString ParameterName,const SVec4 & Value);

    //设置着色器中使用的二维纹理参数的值
    RFUNCTION()
    void SetTexture2DParameter(CString ParameterName,TSharedObjectPtr<RTexture2D> Texture);

    //替换着色器中使用的二维纹理,Index是纹理在材质中的索引
    void SetTexture2DByIndex(int64_t Index,TSharedObjectPtr<RTexture2D> Texture);

    //设置着色器中使用的立方体纹理参数的值
    RFUNCTION()
    void SetTextureCubeParameter(CString ParameterName,TSharedObjectPtr<RTextureCube> Texture);

    //替换着色器中使用的立方体贴图,Index是纹理在材质中的索引
    void SetTextureCubeByIndex(int64_t Index,TSharedObjectPtr<RTextureCube> Texture);

protected:

    RFIELD()
    ERenderMaterialType Type=ERenderMaterialType::Opaque;
    
    //是否渲染背面
    RFIELD()
    bool bDoubleSide=false;

    RFIELD()
    TSharedObjectPtr<RShader> VertexShader;
    RFIELD()
    TSharedObjectPtr<RShader> FragmentShader;
    
    //着色器中使用的由用户定义的所有浮点参数
    RFIELD()
    TVector<float> FloatParameters;

    //描述着色器中使用的所有参数的类型和值的位置
    RFIELD()
    TMap<CString,SMaterialParameterDescription> ParameterDescriptions;

    //着色器中使用的所有二维纹理
    //虽然这个数组里的纹理在运行时都可以替换，并不是所有纹理都被视为动态参数，有参数名的纹理才被视为动态参数
    RFIELD()
    TVector<TSharedObjectPtr<RTexture2D>> Texture2Ds;

    //着色器中使用的所有立方体纹理
    //虽然这个数组里的纹理在运行时都可以替换，并不是所有纹理都被视为动态参数，有参数名的纹理才被视为动态参数
    RFIELD()
    TVector<TSharedObjectPtr<RTextureCube>> TextureCubes;
    

    std::shared_ptr<CRenderMaterial> RenderMaterial;


    friend class RMaterialSubsystem;
    friend class RMaterialGraphModel; //for editor use

};

